



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    
    <title>handler模块(100%) &mdash; Nginx开发从入门到精通</title>
    <link rel="stylesheet" href="_static/book.css" type="text/css" />
    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
    <link rel="stylesheet" href="_static/comment.css" type="text/css" />
    <script type="text/javascript">
      var DOCUMENTATION_OPTIONS = {
        URL_ROOT:    '',
        VERSION:     '0.1',
        COLLAPSE_INDEX: false,
        FILE_SUFFIX: '.html',
        HAS_SOURCE:  true
      };
    </script>
    <script type="text/javascript" src="../js/??ga.js,correctpng.js?v=1"></script>
    <script type="text/javascript" src="_static/jquery.js"></script>
    <script type="text/javascript" src="_static/underscore.js"></script>
    <script type="text/javascript" src="_static/doctools.js"></script>
    <script type="text/javascript" src="_static/booktools.js"></script>
    <script type="text/javascript" src="_static/translations.js"></script>
    <script type="text/javascript" src="_static/comment.js"></script>
    <link rel="top" title="Nginx开发从入门到精通" href="index.html" />
    <link rel="up" title="上篇：nginx模块开发篇" href="module_development.html" />
    <link rel="next" title="过滤模块 (90%)" href="chapter_4.html" />
    <link rel="prev" title="nginx平台初探(30%)" href="chapter_2.html" /> 
  </head>
  <body lang="zh-Hans">
    <div class="related">
      <h3>导航</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="genindex.html" title="总目录"
             accesskey="I">索引</a></li>
        <li class="right" >
          <a href="chapter_4.html" title="过滤模块 (90%)"
             accesskey="N">下一页</a> |</li>
        <li class="right" >
          <a href="chapter_2.html" title="nginx平台初探(30%)"
             accesskey="P">上一页</a> |</li>
        <li><a href="index.html">Nginx开发从入门到精通</a> &raquo;</li>
          <li><a href="module_development.html" accesskey="U">上篇：nginx模块开发篇</a> &raquo;</li> 
      </ul>
    </div>  

    <div class="document">
      <div class="documentwrapper">
        <div class="bodywrapper">
          <div class="body">
            
  <div class="section" id="handler-100">
<h1>handler模块(100%)<a class="headerlink" href="#handler-100" title="永久链接至标题">¶</a></h1>
<div class="section" id="handler">
<h2>handler模块简介<a class="headerlink" href="#handler" title="永久链接至标题">¶</a></h2>
<p>相信大家在看了前一章的模块概述以后，都对nginx的模块有了一个基本的认识。基本上作为第三方开发者最可能开发的就是三种类型的模块，即handler，filter和load-balancer。Handler模块就是接受来自客户端的请求并产生输出的模块。至于有些地方说的upstream模块则实际上也是一种handler。只不过它产生的内容来自于从后端服务器获取的，而非在本机产生的。</p>
<p>当Nginx系统启动的时候，每个handler都有一次机会把自己关联到一个在配置文件中使用location指令配置的一个location上。如果有多个handler模块都去关联同一个location，那么实际上只有一个handler模块真正会起作用。当然大多数情况下，模块开发人员都会避免出现这种情况。</p>
<p>一个handler处理的结果通常有三种情况。处理成功，处理失败（处理的时候发生了错误）或者是拒绝去处理。在拒绝处理的情况下，这个location的处理就会由默认的handler来进行处理。例如，当在请求一个静态文件的时候，如果你关联一个handler到这个location上，但是拒绝处理，就会由默认的ngx_http_static_module模块进行处理，该模块是一个典型的handler。</p>
</div>
<div class="section" id="id1">
<h2>模块的基本结构<a class="headerlink" href="#id1" title="永久链接至标题">¶</a></h2>
<p>在这一节我们将会对通常的模块开发过程中，每个模块所包含的一些常用的部分进行说明。这些部分有些是必须的，有些不是必须的。同时这里所列出的这些东西对于其他类型的模块，例如filter模块等也都是相同的。</p>
<div class="section" id="id2">
<h3>模块配置结构<a class="headerlink" href="#id2" title="永久链接至标题">¶</a></h3>
<p>基本上每个模块都会提供一些配置指令，以便于用户可以通过配置来控制该模块的行为。那么这些配置信息怎么存储呢？那就需要定义该模块的配置结构来进行存储。</p>
<p>大家都知道Nginx的配置信息分成了几个scope，这就是main, server, 以及location。同样的每个模块提供的配置指令也可以出现在这几个scope里。那对于这三个scope的配置信息，每个模块就需要定义三个不同的数据结构去进行存储。当然，不是每个模块都会在这三个scope都提供配置指令的。那么也就不一定每个模块都需要定义三个数据结构去存储这些配置信息了。视模块的实现而言，需要几个就定义几个。</p>
<p>有一点需要特别注意的就是，在模块的开发过程中，我们最好使用nginx原有的命名习惯。这样跟原代码的契合度更高，看起来也更舒服。</p>
<p>对于模块配置信息的定义，命名习惯是ngx_http_&lt;module name&gt;_(main|srv|loc)_conf_t。这里有个例子，就是从我们后面将要展示给大家的hello module中截取的。</p>
<div class="highlight-none"><div class="highlight"><pre>typedef struct
{
    ngx_str_t hello_string;
    ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;
</pre></div>
</div>
</div>
<div class="section" id="id3">
<h3>模块配置指令<a class="headerlink" href="#id3" title="永久链接至标题">¶</a></h3>
<p>一个模块的配置指令是定义在一个静态数组中的。同样地，我们来看一下从hello module中截取的模块配置指令的定义。</p>
<div class="highlight-none"><div class="highlight"><pre>static ngx_command_t ngx_http_hello_commands[] = {
   {
        ngx_string(&quot;hello_string&quot;),
        NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
        ngx_http_hello_string,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_hello_loc_conf_t, hello_string),
        NULL },

    {
        ngx_string(&quot;hello_counter&quot;),
        NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_http_hello_counter,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_hello_loc_conf_t, hello_counter),
        NULL },

    ngx_null_command
};
</pre></div>
</div>
<p>其实看这个定义，就基本能看出来一些信息。例如，我们是定义了两个配置指令，一个是叫hello_string，可以接受一个参数，或者是没有参数。另外一个是hello_counter的参数。除此之外，似乎看起来有点迷惑。没有关系，我们来详细看一下ngx_command_t，一旦我们了解这个结构的详细信息，那么我相信上述这个定义所表达的所有信息就不言自明了。</p>
<p>ngx_command_t的定义，位于src/core/ngx_conf_file.h中。</p>
<div class="highlight-none"><div class="highlight"><pre>struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};
</pre></div>
</div>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">name:</th><td class="field-body">配置指令的名称。</td>
</tr>
<tr class="field-even field"><th class="field-name">type:</th><td class="field-body">该配置的类型，其实更准确一点说，是该配置指令属性的集合。nginx提供了很多预定义的属性值（一些宏定义），通过逻辑或运算符可组合在一起，形成对这个配置指令的详细的说明。下面列出可在这里使用的预定义属性值及说明。</td>
</tr>
</tbody>
</table>
<ul>
<li><p class="first">NGX_CONF_NOARGS：配置指令不接受任何参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE1：配置指令接受1个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE2：配置指令接受2个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE3：配置指令接受3个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE4：配置指令接受4个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE5：配置指令接受5个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE6：配置指令接受6个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE7：配置指令接受7个参数。</p>
<p>可以组合多个属性，比如一个指令即可以不填参数，也可以接受1个或者2个参数。那么就是NGX_CONF_NOARGS|NGX_CONF_TAKE1|NGX_CONF_TAKE2。如果写上面三个属性在一起，你觉得麻烦，那么没有关系，nginx提供了一些定义，使用起来更简洁。</p>
</li>
<li><p class="first">NGX_CONF_TAKE12：配置指令接受1个或者2个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE13：配置指令接受1个或者3个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE23：配置指令接受2个或者3个参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE123：配置指令接受1个或者2个或者3参数。</p>
</li>
<li><p class="first">NGX_CONF_TAKE1234：配置指令接受1个或者2个或者3个或者4个参数。</p>
</li>
<li><p class="first">NGX_CONF_1MORE：配置指令接受至少一个参数。</p>
</li>
<li><p class="first">NGX_CONF_2MORE：配置指令接受至少两个参数。</p>
</li>
<li><p class="first">NGX_CONF_MULTI: 配置指令可以接受多个参数，即个数不定。</p>
</li>
<li><p class="first">NGX_CONF_BLOCK：配置指令可以接受的值是一个配置信息块。也就是一对大括号括起来的内容。里面可以再包括很多的配置指令。比如常见的server指令就是这个属性的。</p>
</li>
<li><p class="first">NGX_CONF_FLAG：配置指令可以接受的值是&#8221;on&#8221;或者&#8221;off&#8221;，最终会被转成bool值。</p>
</li>
<li><p class="first">NGX_CONF_ANY：配置指令可以接受的任意的参数值。一个或者多个，或者&#8221;on&#8221;或者&#8221;off&#8221;，或者是配置块。</p>
<p>最后要说明的是，无论如何，nginx的配置指令的参数个数不可以超过NGX_CONF_MAX_ARGS个。目前这个值被定义为8，也就是不能超过8个参数值。</p>
<p>下面介绍一组说明配置指令可以出现的位置的属性。</p>
</li>
<li><p class="first">NGX_DIRECT_CONF：可以出现在配置文件中最外层。例如已经提供的配置指令daemon，master_process等。</p>
</li>
<li><p class="first">NGX_MAIN_CONF: http、mail、events、error_log等。</p>
</li>
<li><p class="first">NGX_ANY_CONF: 该配置指令可以出现在任意配置级别上。</p>
<p>对于我们编写的大多数模块而言，都是在处理http相关的事情，也就是所谓的都是NGX_HTTP_MODULE，对于这样类型的模块，其配置可能出现的位置也是分为直接出现在http里面，以及其他位置。</p>
</li>
<li><p class="first">NGX_HTTP_MAIN_CONF: 可以直接出现在http配置指令里。</p>
</li>
<li><p class="first">NGX_HTTP_SRV_CONF: 可以出现在http里面的server配置指令里。</p>
</li>
<li><p class="first">NGX_HTTP_LOC_CONF: 可以出现在http里面的location配置指令里。</p>
</li>
<li><p class="first">NGX_HTTP_UPS_CONF: 可以出现在http里面的upstream配置指令里。</p>
</li>
<li><p class="first">NGX_HTTP_SIF_CONF: 可以出现在http里面的server配置指令里的if语句所在的block中。</p>
</li>
<li><p class="first">NGX_HTTP_LIF_CONF: 可以出现在http里面的limit_except指令的block中。</p>
</li>
</ul>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">set:</th><td class="field-body">这是一个函数指针，当nginx在解析配置的时候，如果遇到这个配置指令，将会把读取到的值传递给这个函数进行分解处理。因为具体每个配置指令的值如何处理，只有定义这个配置指令的人是最清楚的。来看一些这个函数指针要求的函数原型。</td>
</tr>
</tbody>
</table>
<div class="highlight-none"><div class="highlight"><pre>char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
</pre></div>
</div>
<p>先看该函数的返回值，处理成功时，返回NGX_OK，否则返回NGX_CONF_ERROR或者是一个自定义的错误信息的字符串。</p>
<p>在看一下这个函数被调用的时候，传入的三个参数。</p>
<ul class="simple">
<li>cf: 该参数里面保存里读取到的配置信息的原始字符串以及相关的一些信息。特别注意的是这个参数的args字段是一个ngx_str_t类型的数组，每个数组元素。该数组的首个元素是这个配置指令本身的字符串，第二个元素是首个参数，第三个元素是第二个参数，依次类推。</li>
<li>cmd: 这个配置指令对应的ngx_command_t结构。</li>
<li>conf: 就是定义的存储这个配置值的结构体，比如在上面展示的那个ngx_http_hello_loc_conf_t。当解析这个hello_string变量的时候，传入的conf就指向一个ngx_http_hello_loc_conf_t类型的变量。用户在处理的时候可以使用类型转换，转换成自己知道的类型，再进行字段的赋值。</li>
</ul>
<p>为了更加方便的实现对配置指令参数的读取，nginx已经默认提供了对一些标准类型的参数进行读取的函数，可以直接赋值个set字段使用。下面来看一下这些已经实现的set类型函数。</p>
<ul class="simple">
<li>ngx_conf_set_flag_slot： 读取NGX_CONF_FLAG类型的参数。</li>
<li>ngx_conf_set_str_slot:读取字符串类型的参数。</li>
<li>ngx_conf_set_str_array_slot: 读取字符串数组类型的参数。</li>
<li>ngx_conf_set_keyval_slot： 读取键值对类型的参数。</li>
<li>ngx_conf_set_num_slot: 读取整数类型(有符号整数ngx_int_t)的参数。</li>
<li>ngx_conf_set_size_slot:读取size_t类型的参数，也就是无符号数。</li>
<li>ngx_conf_set_off_slot: 读取off_t类型的参数。</li>
<li>ngx_conf_set_msec_slot: 读取毫秒值类型的参数。</li>
<li>ngx_conf_set_sec_slot: 读取秒值类型的参数。</li>
<li>ngx_conf_set_bufs_slot： 读取的参数值是2个，一个是buf的个数，一个是buf的大小。例如： output_buffers 1 128k;</li>
<li>ngx_conf_set_enum_slot: 读取枚举类型的参数，将其转换成整数ngx_uint_t类型。</li>
<li>ngx_conf_set_bitmask_slot: 读取参数的值，并将这些参数的值以bit位的形式存储。例如：HttpDavModule模块的dav_methods指令。</li>
</ul>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">conf:</th><td class="field-body">该字段被NGX_HTTP_MODULE类型模块所用 (我们编写的基本上都是NGX_HTTP_MOUDLE，只有一些nginx核心模块是非NGX_HTTP_MODULE)，该字段指定当前配置项存储的内存位置。实际上是使用哪个内存池的问题。因为http模块对所有http模块所要保存的配置信息，划分了main, server和location三个地方进行存储，每个地方都有一个内存池用来分配存储这些信息的内存。这里可能的值为 NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET。当然也可以直接置为0，就是NGX_HTTP_MAIN_CONF_OFFSET。</td>
</tr>
<tr class="field-even field"><th class="field-name">offset:</th><td class="field-body">指定该配置项值的精确存放位置，一般指定为某一个结构体变量的字段偏移。因为对于配置信息的存储，一般我们都是定义个结构体来存储的。那么比如我们定义了一个结构体A，该项配置的值需要存储到该结构体的b字段。那么在这里就可以填写为offsetof(A, b)。对于有些配置项，它的值不需要保存或者是需要保存到更为复杂的结构中时，这里可以设置为0。</td>
</tr>
<tr class="field-odd field"><th class="field-name">post:</th><td class="field-body">该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据，以便于进行配置读取的处理。大多数时候，都不需要，所以简单地设为0即可。</td>
</tr>
</tbody>
</table>
<p>看到这里，应该就比较清楚了。ngx_http_hello_commands这个数组每5个元素为一组，用来描述一个配置项的所有情况。那么如果有多个配置项，只要按照需要再增加5个对应的元素对新的配置项进行说明。</p>
<p><strong>需要注意的是，就是在ngx_http_hello_commands这个数组定义的最后，都要加一个ngx_null_command作为结尾。</strong></p>
</div>
<div class="section" id="id4">
<h3>模块上下文结构<a class="headerlink" href="#id4" title="永久链接至标题">¶</a></h3>
<p>这是一个ngx_http_module_t类型的静态变量。这个变量实际上是提供一组回调函数指针，这些函数有在创建存储配置信息的对象的函数，也有在创建前和创建后会调用的函数。这些函数都将被nginx在合适的时间进行调用。</p>
<div class="highlight-none"><div class="highlight"><pre>typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
</pre></div>
</div>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name" colspan="2">preconfiguration:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">在创建和读取该模块的配置信息之前被调用。</td>
</tr>
<tr class="field-even field"><th class="field-name" colspan="2">postconfiguration:</th></tr>
<tr class="field-even field"><td>&nbsp;</td><td class="field-body">在创建和读取该模块的配置信息之后被调用。</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">create_main_conf:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">调用该函数创建本模块位于http block的配置信息存储结构。该函数成功的时候，返回创建的配置对象。失败的话，返回NULL。</td>
</tr>
<tr class="field-even field"><th class="field-name">init_main_conf:</th><td class="field-body">调用该函数初始化本模块位于http block的配置信息存储结构。该函数成功的时候，返回NGX_CONF_OK。失败的话，返回NGX_CONF_ERROR或错误字符串。</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">create_srv_conf:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">调用该函数创建本模块位于http server block的配置信息存储结构，每个server block会创建一个。该函数成功的时候，返回创建的配置对象。失败的话，返回NULL。</td>
</tr>
<tr class="field-even field"><th class="field-name">merge_srv_conf:</th><td class="field-body">因为有些配置指令即可以出现在http block，也可以出现在http server block中。那么遇到这种情况，每个server都会有自己存储结构来存储该server的配置，但是在这种情况下当在http block中的配置与server block中的配置信息冲突的时候，就需要调用此函数进行合并，该函数并非必须提供，当预计到绝对不会发生需要合并的情况的时候，就无需提供。当然为了安全期间还是建议提供。该函数成功的时候，返回NGX_CONF_OK。失败的话，返回NGX_CONF_ERROR或错误字符串。</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">create_loc_conf:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">调用该函数创建本模块位于location block的配置信息存储结构。每个在配置中指明的location创建一个。该函数成功的时候，返回创建的配置对象。失败的话，返回NULL。</td>
</tr>
<tr class="field-even field"><th class="field-name">merge_loc_conf:</th><td class="field-body">与merge_srv_conf类似，这个也是进行配置值合并的地方。该函数成功的时候，返回NGX_CONF_OK。失败的话，返回NGX_CONF_ERROR或错误字符串。</td>
</tr>
</tbody>
</table>
<p>Nginx里面的配置信息都是上下一层层的嵌套的，对于具体某个location的话，对于同一个配置，如果自己这里没有定义，那么就使用上层的配置，否则是用自己的配置。</p>
<p>这些配置信息一般默认都应该设为一个未初始化的值，针对这个需求，Nginx定义了一系列的宏定义来代表个中配置所对应数据类型的未初始化值，如下：</p>
<div class="highlight-none"><div class="highlight"><pre>#define NGX_CONF_UNSET       -1
#define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
#define NGX_CONF_UNSET_PTR   (void *) -1
#define NGX_CONF_UNSET_SIZE  (size_t) -1
#define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1
</pre></div>
</div>
<p>又因为对于配置项的合并，逻辑都类似，也就是前面已经说过的，如果在本层次已经配置了，也就是配置项的值已经被读取进来了（那么这些配置项的值就不会等于上面已经定义的那些UNSET的值），就使用本层次的值作为定义合并的结果，否则，使用上层的值，如果上层的值也是这些UNSET类的值，那就复制为默认值，否则就是用上层的值作为合并的结果。对于这样类似的操作，Nginx定义了一些宏操作来做这些事情，我们来看其中一个的定义。</p>
<div class="highlight-none"><div class="highlight"><pre>#define ngx_conf_merge_uint_value(conf, prev, default)                       \
    if (conf == NGX_CONF_UNSET_UINT) {                                       \
        conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev;               \
    }
</pre></div>
</div>
<p>显而易见，这个逻辑确实比较简单，所以其它的宏定义也类似，我们就列具其中的一部分吧。</p>
<div class="highlight-none"><div class="highlight"><pre>ngx_conf_merge_value
ngx_conf_merge_ptr_value
ngx_conf_merge_uint_value
ngx_conf_merge_msec_value
ngx_conf_merge_sec_value
</pre></div>
</div>
<p>等等。</p>
<p>下面来看一下hello模块的模块上下文的定义，加深一下印象。</p>
<div class="highlight-none"><div class="highlight"><pre>static ngx_http_module_t ngx_http_hello_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_hello_init,           /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    ngx_http_hello_create_loc_conf, /* create location configuration */
    NULL                        /* merge location configuration */
};
</pre></div>
</div>
<p><strong>注意：这里并没有提供merge_loc_conf函数，因为我们这个模块的配置指令已经确定只出现在NGX_HTTP_LOC_CONF中这一个level上，不会发生需要合并的情况。</strong></p>
</div>
<div class="section" id="id5">
<h3>模块的定义<a class="headerlink" href="#id5" title="永久链接至标题">¶</a></h3>
<p>对于开发一个模块来说，我们都需要定义一个ngx_module_t类型的变量来说明这个模块本身的信息，从某种意义上来说，这是这个模块最重要的一个信息，它告诉了nginx这个模块的一些信息，上面定义的配置信息，还有模块上下文信息，都是通过这个结构来告诉nginx系统的，也就是加载模块的上层代码，都需要通过定义的这个结构，来获取这些信息。</p>
<p>我们来看一下hello模块的模块定义。</p>
<div class="highlight-none"><div class="highlight"><pre>ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &amp;ngx_http_hello_module_ctx,    /* module context */
    ngx_http_hello_commands,       /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};
</pre></div>
</div>
<p>模块可以提供一些回调函数给nginx，当nginx在创建进程线程或者结束进程线程时进行调用。但大多数模块在这些时刻并不需奥做写什么事情，所以都简单赋值为NULL。</p>
</div>
</div>
<div class="section" id="id6">
<h2>handler模块的基本结构<a class="headerlink" href="#id6" title="永久链接至标题">¶</a></h2>
<p>除了上一节介绍的模块的基本结构以外，handler模块必须提供一个真正的处理函数，这个函数负责对来自客户端请求的真正处理。这个函数的处理，即可以选择自己直接生成内容，也可以选择拒绝处理，由后续的handler去进行处理，或者是选择丢给后续的filter进行处理。来看一下这个函数的原型申明。</p>
<p>typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t  *r);</p>
<p>r是http请求。里面包含请求所有的信息，这里不相信说明了，可以参考别的章节的介绍。
该函数处理成功返回NGX_OK，处理发生错误返回NGX_ERROR，拒绝处理（留给后续的handler进行处理）返回NGX_DECLINE。
返回NGX_OK也就代表给客户端的响应已经生成好了，否则返回NGX_OK就发生错误了。</p>
</div>
<div class="section" id="id7">
<h2>handler模块的挂载<a class="headerlink" href="#id7" title="永久链接至标题">¶</a></h2>
<div class="section" id="id8">
<h3>按处理阶段挂载<a class="headerlink" href="#id8" title="永久链接至标题">¶</a></h3>
<p>为了更精细地控制对于客户端请求的处理过程，nginx把这个处理过程划分成了11个阶段。他们从前到后，依次列举如下：</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name" colspan="2">NGX_HTTP_POST_READ_PHASE:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">读取请求内容阶段</td>
</tr>
<tr class="field-even field"><th class="field-name" colspan="2">NGX_HTTP_SERVER_REWRITE_PHASE:</th></tr>
<tr class="field-even field"><td>&nbsp;</td><td class="field-body">Server请求地址重写阶段</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">NGX_HTTP_FIND_CONFIG_PHASE:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">配置查找阶段:</td>
</tr>
<tr class="field-even field"><th class="field-name" colspan="2">NGX_HTTP_REWRITE_PHASE:</th></tr>
<tr class="field-even field"><td>&nbsp;</td><td class="field-body">Location请求地址重写阶段</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">NGX_HTTP_POST_REWRITE_PHASE:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">请求地址重写提交阶段</td>
</tr>
<tr class="field-even field"><th class="field-name" colspan="2">NGX_HTTP_PREACCESS_PHASE:</th></tr>
<tr class="field-even field"><td>&nbsp;</td><td class="field-body">访问权限检查准备阶段</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">NGX_HTTP_ACCESS_PHASE:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">访问权限检查阶段</td>
</tr>
<tr class="field-even field"><th class="field-name" colspan="2">NGX_HTTP_POST_ACCESS_PHASE:</th></tr>
<tr class="field-even field"><td>&nbsp;</td><td class="field-body">访问权限检查提交阶段</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">NGX_HTTP_TRY_FILES_PHASE:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">配置项try_files处理阶段</td>
</tr>
<tr class="field-even field"><th class="field-name" colspan="2">NGX_HTTP_CONTENT_PHASE:</th></tr>
<tr class="field-even field"><td>&nbsp;</td><td class="field-body">内容产生阶段</td>
</tr>
<tr class="field-odd field"><th class="field-name" colspan="2">NGX_HTTP_LOG_PHASE:</th></tr>
<tr class="field-odd field"><td>&nbsp;</td><td class="field-body">日志模块处理阶段</td>
</tr>
</tbody>
</table>
<p>一般情况下，我们自定义的模块，大多数是挂载在NGX_HTTP_CONTENT_PHASE阶段的。挂载的动作一般是现在模块上下文调用的postconfiguration函数中。</p>
<p><strong>注意：有几个阶段是特例，它不调用挂载地任何的handler，也就是你就不用挂载到这几个阶段了：</strong></p>
<ul class="simple">
<li>NGX_HTTP_FIND_CONFIG_PHASE</li>
<li>NGX_HTTP_POST_ACCESS_PHASE</li>
<li>NGX_HTTP_POST_REWRITE_PHASE</li>
<li>NGX_HTTP_TRY_FILES_PHASE</li>
</ul>
<p>所以其实真正是有6个phase你可以去挂载handler。</p>
<p>挂载的代码如下（摘自hello module）:</p>
<div class="highlight-none"><div class="highlight"><pre>static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
        ngx_http_handler_pt        *h;
        ngx_http_core_main_conf_t  *cmcf;

        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

        h = ngx_array_push(&amp;cmcf-&gt;phases[NGX_HTTP_CONTENT_PHASE].handlers);
        if (h == NULL) {
                return NGX_ERROR;
        }

        *h = ngx_http_hello_handler;

        return NGX_OK;
}
</pre></div>
</div>
<p>使用这种方式挂载的handler也被称为 <strong>content phase handlers</strong>。</p>
</div>
<div class="section" id="id9">
<h3>按需挂载<a class="headerlink" href="#id9" title="永久链接至标题">¶</a></h3>
<p>以这种方式挂载的handler也被称为 <strong>content handler</strong>。</p>
<p>一个请求进来以后，nginx按照从NGX_HTTP_POST_READ_PHASE开始的阶段，去依次执行每个阶段的所有handler。等到执行到 NGX_HTTP_CONTENT_PHASE阶段的时候，如果这个location对应的有一个content handler，那么就去执行这个content handler。否则去依次执行NGX_HTTP_CONTENT_PHASE阶段挂载的所有content phase handlers，直到某个函数处理返回NGX_OK或者NGX_ERROR。</p>
<p>换句话说，如果某个location在处理到NGX_HTTP_CONTENT_PHASE阶段的时候，如果有content handler，那么所有的挂载的content phase handlers都不会被执行了。</p>
<p>使用这个方法挂载上去的handler，必须在NGX_HTTP_CONTENT_PHASE阶段才能执行到。如果你想自己的handler要被更早的执行到的话，那就不要使用这种挂载方式。</p>
<p>另外要提一下，在什么情况会使用这种方式来挂载。一般就是某个模块如果对某个location进行了处理以后，发现符合自己处理的逻辑，而且也没有必要再调用NGX_HTTP_CONTENT_PHASE阶段的其它handler进行处理的时候，就动态挂载上这个handler。</p>
<p>好了，下面看一下这种挂载方式的具体代码（摘自Emiller&#8217;s Guide To Nginx Module Development）。</p>
<div class="highlight-none"><div class="highlight"><pre>static char *
ngx_http_circle_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
        ngx_http_core_loc_conf_t  *clcf;

        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
        clcf-&gt;handler = ngx_http_circle_gif_handler;

        return NGX_CONF_OK;
}
</pre></div>
</div>
</div>
</div>
<div class="section" id="id10">
<h2>handler的编写步骤<a class="headerlink" href="#id10" title="永久链接至标题">¶</a></h2>
<p>好，到了这里，让我们稍微整理一下思路，回顾一下实现一个handler的步骤:</p>
<ol class="arabic simple">
<li>编写模块基本结构。</li>
<li>实现handler的挂载函数。</li>
<li>编写handler处理函数。</li>
</ol>
<p>看起来不是那么难，对吧？还是那句老话，世上无难事，只怕有心人!</p>
</div>
<div class="section" id="hello-handler">
<h2>hello handler 模块<a class="headerlink" href="#hello-handler" title="永久链接至标题">¶</a></h2>
<p>我们在前面已经看到了这个hello handler module的部分重要的结构。现在我们完整的介绍一下这个示例模块的功能和代码。</p>
<p>该模块提供了2个配置指令，仅可以出现在location指令的block中。这两个指令是hello_string, 该参数接受一个参数来设置显示的字符串。如果没有跟参数，那么就使用默认的字符串作为响应字符串。</p>
<p>另一个参数是hello_counter，如果设置为on，则会在响应的字符串后面追加Visited Times:的字样，以统计请求的次数。</p>
<p>这里有两点注意一下：</p>
<ol class="arabic simple">
<li>对于flag类型的配置指令，当值为off的时候，使用ngx_conf_set_flag_slot函数，会转化为0，为on，则转化为非0。</li>
<li>另外一个是，我提供了merge_loc_conf函数，但是却没有设置到模块的上下文定义中。这样有一个缺点，就是如果一个指令没有出现在配置文件中的时候，配置信息中的值，将永远会保持在create_loc_conf中的初始化的值。那如果，在类似create_loc_conf这样的函数中，对创建出来的配置信息的值，没有设置为合理的值的话，后面用户又没有配置，就会出现问题。</li>
</ol>
<p>下面来完整的给出ngx_http_hello_module模块的完整代码。</p>
<div class="highlight-none"><div class="highlight"><pre>#include &lt;ngx_config.h&gt;
#include &lt;ngx_core.h&gt;
#include &lt;ngx_http.h&gt;


typedef struct
{
        ngx_str_t hello_string;
        ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;

static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf);

static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);

static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd,
        void *conf);
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd,
        void *conf);

static ngx_command_t ngx_http_hello_commands[] = {
   {
                ngx_string(&quot;hello_string&quot;),
                NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
                ngx_http_hello_string,
                NGX_HTTP_LOC_CONF_OFFSET,
                offsetof(ngx_http_hello_loc_conf_t, hello_string),
                NULL },

        {
                ngx_string(&quot;hello_counter&quot;),
                NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
                ngx_http_hello_counter,
                NGX_HTTP_LOC_CONF_OFFSET,
                offsetof(ngx_http_hello_loc_conf_t, hello_counter),
                NULL },

        ngx_null_command
};


/*
static u_char ngx_hello_default_string[] = &quot;Default String: Hello, world!&quot;;
*/
static int ngx_hello_visited_times = 0;

static ngx_http_module_t ngx_http_hello_module_ctx = {
        NULL,                          /* preconfiguration */
        ngx_http_hello_init,           /* postconfiguration */

        NULL,                          /* create main configuration */
        NULL,                          /* init main configuration */

        NULL,                          /* create server configuration */
        NULL,                          /* merge server configuration */

        ngx_http_hello_create_loc_conf, /* create location configuration */
        NULL                            /* merge location configuration */
};


ngx_module_t ngx_http_hello_module = {
        NGX_MODULE_V1,
        &amp;ngx_http_hello_module_ctx,    /* module context */
        ngx_http_hello_commands,       /* module directives */
        NGX_HTTP_MODULE,               /* module type */
        NULL,                          /* init master */
        NULL,                          /* init module */
        NULL,                          /* init process */
        NULL,                          /* init thread */
        NULL,                          /* exit thread */
        NULL,                          /* exit process */
        NULL,                          /* exit master */
        NGX_MODULE_V1_PADDING
};


static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
        ngx_int_t    rc;
        ngx_buf_t   *b;
        ngx_chain_t  out;
        ngx_http_hello_loc_conf_t* my_conf;
        u_char ngx_hello_string[1024] = {0};
        ngx_uint_t content_length = 0;

        ngx_log_error(NGX_LOG_EMERG, r-&gt;connection-&gt;log, 0, &quot;ngx_http_hello_handler is called!&quot;);

        my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
        if (my_conf-&gt;hello_string.len == 0 )
        {
                ngx_log_error(NGX_LOG_EMERG, r-&gt;connection-&gt;log, 0, &quot;hello_string is empty!&quot;);
                return NGX_DECLINED;
        }


        if (my_conf-&gt;hello_counter == NGX_CONF_UNSET
                || my_conf-&gt;hello_counter == 0)
        {
                ngx_sprintf(ngx_hello_string, &quot;%s&quot;, my_conf-&gt;hello_string.data);
        }
        else
        {
                ngx_sprintf(ngx_hello_string, &quot;%s Visited Times:%d&quot;, my_conf-&gt;hello_string.data,
                        ++ngx_hello_visited_times);
        }
        ngx_log_error(NGX_LOG_EMERG, r-&gt;connection-&gt;log, 0, &quot;hello_string:%s&quot;, ngx_hello_string);
        content_length = ngx_strlen(ngx_hello_string);

        /* we response to &#39;GET&#39; and &#39;HEAD&#39; requests only */
        if (!(r-&gt;method &amp; (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
                return NGX_HTTP_NOT_ALLOWED;
        }

        /* discard request body, since we don&#39;t need it here */
        rc = ngx_http_discard_request_body(r);

        if (rc != NGX_OK) {
                return rc;
        }

        /* set the &#39;Content-type&#39; header */
        /*
        r-&gt;headers_out.content_type_len = sizeof(&quot;text/html&quot;) - 1;
        r-&gt;headers_out.content_type.len = sizeof(&quot;text/html&quot;) - 1;
        r-&gt;headers_out.content_type.data = (u_char *)&quot;text/html&quot;;*/
        ngx_str_set(&amp;r-&gt;headers_out.content_type, &quot;text/html&quot;);


        /* send the header only, if the request type is http &#39;HEAD&#39; */
        if (r-&gt;method == NGX_HTTP_HEAD) {
                r-&gt;headers_out.status = NGX_HTTP_OK;
                r-&gt;headers_out.content_length_n = content_length;

                return ngx_http_send_header(r);
        }

        /* allocate a buffer for your response body */
        b = ngx_pcalloc(r-&gt;pool, sizeof(ngx_buf_t));
        if (b == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        /* attach this buffer to the buffer chain */
        out.buf = b;
        out.next = NULL;

        /* adjust the pointers of the buffer */
        b-&gt;pos = ngx_hello_string;
        b-&gt;last = ngx_hello_string + content_length;
        b-&gt;memory = 1;    /* this buffer is in memory */
        b-&gt;last_buf = 1;  /* this is the last buffer in the buffer chain */

        /* set the status line */
        r-&gt;headers_out.status = NGX_HTTP_OK;
        r-&gt;headers_out.content_length_n = content_length;

        /* send the headers of your response */
        rc = ngx_http_send_header(r);

        if (rc == NGX_ERROR || rc &gt; NGX_OK || r-&gt;header_only) {
                return rc;
        }

        /* send the buffer chain of your response */
        return ngx_http_output_filter(r, &amp;out);
}

static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
        ngx_http_hello_loc_conf_t* local_conf = NULL;
        local_conf = ngx_pcalloc(cf-&gt;pool, sizeof(ngx_http_hello_loc_conf_t));
        if (local_conf == NULL)
        {
                return NULL;
        }

        ngx_str_null(&amp;local_conf-&gt;hello_string);
        local_conf-&gt;hello_counter = NGX_CONF_UNSET;

        return local_conf;
}

/*
static char *ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
        ngx_http_hello_loc_conf_t* prev = parent;
        ngx_http_hello_loc_conf_t* conf = child;

        ngx_conf_merge_str_value(conf-&gt;hello_string, prev-&gt;hello_string, ngx_hello_default_string);
        ngx_conf_merge_value(conf-&gt;hello_counter, prev-&gt;hello_counter, 0);

        return NGX_CONF_OK;
}*/

static char *
ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
        ngx_http_core_loc_conf_t *clcf;
        ngx_http_hello_loc_conf_t* local_conf;

        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

        local_conf = conf;
        char* rv = ngx_conf_set_str_slot(cf, cmd, conf);

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, &quot;hello_string:%s&quot;, local_conf-&gt;hello_string.data);

        return rv;
}


static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd,
        void *conf)
{
        ngx_http_hello_loc_conf_t* local_conf;
        ngx_http_core_loc_conf_t *clcf;

        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

        local_conf = conf;

        char* rv = NULL;

        rv = ngx_conf_set_flag_slot(cf, cmd, conf);


        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, &quot;hello_counter:%d&quot;, local_conf-&gt;hello_counter);
        return rv;
}

static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
        ngx_http_handler_pt        *h;
        ngx_http_core_main_conf_t  *cmcf;

        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

        h = ngx_array_push(&amp;cmcf-&gt;phases[NGX_HTTP_CONTENT_PHASE].handlers);
        if (h == NULL) {
                return NGX_ERROR;
        }

        *h = ngx_http_hello_handler;

        return NGX_OK;
}
</pre></div>
</div>
<p>通过上面一些介绍，我相信大家都能对整个程序有一个比较好的理解。唯一可能感觉有些理解困难的地方在于ngx_http_hello_handler函数里面产生和设置输出。但其实大家在本书的前面的相关章节都可以看到对ngx_buf_t和request等相关数据结构的说明。如果仔细看了这些地方的说明的话，应该对这里代码的实现就比较容易理解了。因此，这里不再赘述解释。</p>
</div>
<div class="section" id="id11">
<h2>handler模块的编译和使用<a class="headerlink" href="#id11" title="永久链接至标题">¶</a></h2>
<div class="section" id="config">
<h3>config文件的编写<a class="headerlink" href="#config" title="永久链接至标题">¶</a></h3>
<p>对于开发一个模块，我们是需要把这个模块的C代码组织到一个目录里，同时需要编写一个config文件。这个config文件的内容就是告诉nginx的编译脚本，该如何进行编译。我们来看一下hello handler module的config文件的内容，然后再做解释。</p>
<div class="highlight-none"><div class="highlight"><pre>ngx_addon_name=ngx_http_hello_module
HTTP_MODULES=&quot;$HTTP_MODULES ngx_http_hello_module&quot;
NGX_ADDON_SRCS=&quot;$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c&quot;
</pre></div>
</div>
<p>其实文件很简单，几乎不需要做什么解释。大家一看都懂了。唯一需要说明的是，如果这个模块的实现有多个源文件，那么都在NGX_ADDON_SRCS这个变量里，依次写进去就可以。</p>
</div>
<div class="section" id="id12">
<h3>编译<a class="headerlink" href="#id12" title="永久链接至标题">¶</a></h3>
<p>对于模块的编译，nginx并不像apache一样，提供了单独的编译工具，可以在没有nginx源代码的情况下来单独编译一个模块的代码。nginx必须去到nginx的源代码目录里，通过configure指令的参数，来进行编译。下面看一下hello module的configure指令：</p>
<p>./configure &#8211;prefix=/usr/local/nginx-1.3.1 &#8211;add-module=/home/jizhao/open_source/book_module</p>
<p>我写的这个示例模块的代码和config文件都放在/home/jizhao/open_source/book_module这个目录下。所以一切都很明了，也没什么好说的了。</p>
</div>
<div class="section" id="id13">
<h3>使用<a class="headerlink" href="#id13" title="永久链接至标题">¶</a></h3>
<p>使用一个模块需要根据这个模块定义的配置指令来做。比如我们这个简单的hello handler module的使用就很简单。在我的测试服务器的配置文件里，就是在http里面的默认的server里面加入如下的配置：</p>
<div class="highlight-none"><div class="highlight"><pre>location /test {
                hello_string jizhao;
                hello_counter on;
}
</pre></div>
</div>
<p>当我们访问这个地址的时候, lynx <a class="reference external" href="http://127.0.0.1">http://127.0.0.1</a>/test的时候，就可以看到返回的结果。</p>
<p>jizhao Visited Times:1</p>
<p>当然你访问多次，这个次数是会增加的。</p>
</div>
</div>
<div class="section" id="id14">
<h2>部分handler模块的分析<a class="headerlink" href="#id14" title="永久链接至标题">¶</a></h2>
<div class="section" id="http-access-module">
<h3>http access module<a class="headerlink" href="#http-access-module" title="永久链接至标题">¶</a></h3>
<p>该模块的代码位于src/http/modules/ngx_http_access_module.c中。该模块的作用是提供对于特定host的客户端的访问控制。可以限定特定host的客户端对于服务端全部，或者某个server，或者是某个location的访问。
该模块的实现非常简单，总共也就只有几个函数。</p>
<div class="highlight-none"><div class="highlight"><pre>static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_access_inet(ngx_http_request_t *r,
        ngx_http_access_loc_conf_t *alcf, in_addr_t addr);
#if (NGX_HAVE_INET6)
static ngx_int_t ngx_http_access_inet6(ngx_http_request_t *r,
        ngx_http_access_loc_conf_t *alcf, u_char *p);
#endif
static ngx_int_t ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny);
static char *ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
        void *conf);
static void *ngx_http_access_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_access_merge_loc_conf(ngx_conf_t *cf,
        void *parent, void *child);
static ngx_int_t ngx_http_access_init(ngx_conf_t *cf);
</pre></div>
</div>
<p>对于与配置相关的几个函数都不需要做解释了，需要提一下的是函数ngx_http_access_init，该函数在实现上把本模块挂载到了NGX_HTTP_ACCESS_PHASE阶段的handler上，从而使自己的被调用时机发生在了NGX_HTTP_CONTENT_PHASE等阶段前。因为进行客户端地址的限制检查，根本不需要等到这么后面。</p>
<p>另外看一下这个模块的主处理函数ngx_http_access_handler。这个函数的逻辑也非常简单，主要是根据客户端地址的类型，来分别选则ipv4类型的处理函数ngx_http_access_inet还是ipv6类型的处理函数ngx_http_access_inet6。</p>
<p>而这个两个处理函数内部也非常简单，就是循环检查每个规则，检查是否有匹配的规则，如果有就返回匹配的结果，如果都没有匹配，就默认拒绝。</p>
</div>
<div class="section" id="http-static-module">
<h3>http static module<a class="headerlink" href="#http-static-module" title="永久链接至标题">¶</a></h3>
<p>从某种程度上来说，此模块可以算的上是“最正宗的”，“最古老”的content handler。因为本模块的作用就是读取磁盘上的静态文件，并把文件内容作为产生的输出。在Web技术发展的早期，只有静态页面，没有服务端脚本来动态生成HTML的时候。恐怕开发个Web服务器的时候，第一个要开发就是这样一个content handler。</p>
<p>http static module的代码位于srchttpmodulesngx_http_static_module.c中，总共只有两百多行近三百行。可以说是非常短小。</p>
<p>我们首先来看一下该模块的模块上下文的定义。</p>
<div class="highlight-none"><div class="highlight"><pre>ngx_http_module_t  ngx_http_static_module_ctx = {
        NULL,                                  /* preconfiguration */
        ngx_http_static_init,                  /* postconfiguration */

        NULL,                                  /* create main configuration */
        NULL,                                  /* init main configuration */

        NULL,                                  /* create server configuration */
        NULL,                                  /* merge server configuration */

        NULL,                                  /* create location configuration */
        NULL                                   /* merge location configuration */
};
</pre></div>
</div>
<p>是非常的简洁吧，连任何与配置相关的函数都没有。对了，因为该模块没有提供任何配置指令。大家想想也就知道了，这个模块做的事情实在是太简单了，也确实没什么好配置的。唯一需要调用的函数是一个ngx_http_static_init函数。好了，来看一下这个函数都干了写什么。</p>
<div class="highlight-none"><div class="highlight"><pre>static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
        ngx_http_handler_pt        *h;
        ngx_http_core_main_conf_t  *cmcf;

        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

        h = ngx_array_push(&amp;cmcf-&gt;phases[NGX_HTTP_CONTENT_PHASE].handlers);
        if (h == NULL) {
                return NGX_ERROR;
        }

        *h = ngx_http_static_handler;

        return NGX_OK;
}
</pre></div>
</div>
<p>仅仅是挂载这个handler到NGX_HTTP_CONTENT_PHASE处理阶段。简单吧？</p>
<p>下面我们就看一下这个模块最核心的处理逻辑所在的ngx_http_static_handler函数。该函数大概占了这个模块代码量的百分之八九十。</p>
<div class="highlight-none"><div class="highlight"><pre>static ngx_int_t
ngx_http_static_handler(ngx_http_request_t *r)
{
        u_char                    *last, *location;
        size_t                     root, len;
        ngx_str_t                  path;
        ngx_int_t                  rc;
        ngx_uint_t                 level;
        ngx_log_t                 *log;
        ngx_buf_t                 *b;
        ngx_chain_t                out;
        ngx_open_file_info_t       of;
        ngx_http_core_loc_conf_t  *clcf;

        if (!(r-&gt;method &amp; (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
                return NGX_HTTP_NOT_ALLOWED;
        }

        if (r-&gt;uri.data[r-&gt;uri.len - 1] == &#39;/&#39;) {
                return NGX_DECLINED;
        }

        log = r-&gt;connection-&gt;log;

        /*
         * ngx_http_map_uri_to_path() allocates memory for terminating &#39;\0&#39;
         * so we do not need to reserve memory for &#39;/&#39; for possible redirect
         */

        last = ngx_http_map_uri_to_path(r, &amp;path, &amp;root, 0);
        if (last == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        path.len = last - path.data;

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                                   &quot;http filename: \&quot;%s\&quot;&quot;, path.data);

        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        ngx_memzero(&amp;of, sizeof(ngx_open_file_info_t));

        of.read_ahead = clcf-&gt;read_ahead;
        of.directio = clcf-&gt;directio;
        of.valid = clcf-&gt;open_file_cache_valid;
        of.min_uses = clcf-&gt;open_file_cache_min_uses;
        of.errors = clcf-&gt;open_file_cache_errors;
        of.events = clcf-&gt;open_file_cache_events;

        if (ngx_http_set_disable_symlinks(r, clcf, &amp;path, &amp;of) != NGX_OK) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (ngx_open_cached_file(clcf-&gt;open_file_cache, &amp;path, &amp;of, r-&gt;pool)
                != NGX_OK)
        {
                switch (of.err) {

                case 0:
                        return NGX_HTTP_INTERNAL_SERVER_ERROR;

                case NGX_ENOENT:
                case NGX_ENOTDIR:
                case NGX_ENAMETOOLONG:

                        level = NGX_LOG_ERR;
                        rc = NGX_HTTP_NOT_FOUND;
                        break;

                case NGX_EACCES:
#if (NGX_HAVE_OPENAT)
                case NGX_EMLINK:
                case NGX_ELOOP:
#endif

                        level = NGX_LOG_ERR;
                        rc = NGX_HTTP_FORBIDDEN;
                        break;

                default:

                        level = NGX_LOG_CRIT;
                        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
                        break;
                }

                if (rc != NGX_HTTP_NOT_FOUND || clcf-&gt;log_not_found) {
                        ngx_log_error(level, log, of.err,
                                                  &quot;%s \&quot;%s\&quot; failed&quot;, of.failed, path.data);
                }

                return rc;
        }

        r-&gt;root_tested = !r-&gt;error_page;

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, &quot;http static fd: %d&quot;, of.fd);

        if (of.is_dir) {

                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, &quot;http dir&quot;);

                ngx_http_clear_location(r);

                r-&gt;headers_out.location = ngx_palloc(r-&gt;pool, sizeof(ngx_table_elt_t));
                if (r-&gt;headers_out.location == NULL) {
                        return NGX_HTTP_INTERNAL_SERVER_ERROR;
                }

                len = r-&gt;uri.len + 1;

                if (!clcf-&gt;alias &amp;&amp; clcf-&gt;root_lengths == NULL &amp;&amp; r-&gt;args.len == 0) {
                        location = path.data + clcf-&gt;root.len;

                        *last = &#39;/&#39;;

                } else {
                        if (r-&gt;args.len) {
                                len += r-&gt;args.len + 1;
                        }

                        location = ngx_pnalloc(r-&gt;pool, len);
                        if (location == NULL) {
                                return NGX_HTTP_INTERNAL_SERVER_ERROR;
                        }

                        last = ngx_copy(location, r-&gt;uri.data, r-&gt;uri.len);

                        *last = &#39;/&#39;;

                        if (r-&gt;args.len) {
                                *++last = &#39;?&#39;;
                                ngx_memcpy(++last, r-&gt;args.data, r-&gt;args.len);
                        }
                }

                /*
                 * we do not need to set the r-&gt;headers_out.location-&gt;hash and
                 * r-&gt;headers_out.location-&gt;key fields
                 */

                r-&gt;headers_out.location-&gt;value.len = len;
                r-&gt;headers_out.location-&gt;value.data = location;

                return NGX_HTTP_MOVED_PERMANENTLY;
        }

#if !(NGX_WIN32) /* the not regular files are probably Unix specific */

        if (!of.is_file) {
                ngx_log_error(NGX_LOG_CRIT, log, 0,
                                          &quot;\&quot;%s\&quot; is not a regular file&quot;, path.data);

                return NGX_HTTP_NOT_FOUND;
        }

#endif

        if (r-&gt;method &amp; NGX_HTTP_POST) {
                return NGX_HTTP_NOT_ALLOWED;
        }

        rc = ngx_http_discard_request_body(r);

        if (rc != NGX_OK) {
                return rc;
        }

        log-&gt;action = &quot;sending response to client&quot;;

        r-&gt;headers_out.status = NGX_HTTP_OK;
        r-&gt;headers_out.content_length_n = of.size;
        r-&gt;headers_out.last_modified_time = of.mtime;

        if (ngx_http_set_content_type(r) != NGX_OK) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (r != r-&gt;main &amp;&amp; of.size == 0) {
                return ngx_http_send_header(r);
        }

        r-&gt;allow_ranges = 1;

        /* we need to allocate all before the header would be sent */

        b = ngx_pcalloc(r-&gt;pool, sizeof(ngx_buf_t));
        if (b == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        b-&gt;file = ngx_pcalloc(r-&gt;pool, sizeof(ngx_file_t));
        if (b-&gt;file == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        rc = ngx_http_send_header(r);

        if (rc == NGX_ERROR || rc &gt; NGX_OK || r-&gt;header_only) {
                return rc;
        }

        b-&gt;file_pos = 0;
        b-&gt;file_last = of.size;

        b-&gt;in_file = b-&gt;file_last ? 1: 0;
        b-&gt;last_buf = (r == r-&gt;main) ? 1: 0;
        b-&gt;last_in_chain = 1;

        b-&gt;file-&gt;fd = of.fd;
        b-&gt;file-&gt;name = path;
        b-&gt;file-&gt;log = log;
        b-&gt;file-&gt;directio = of.is_directio;

        out.buf = b;
        out.next = NULL;

        return ngx_http_output_filter(r, &amp;out);
}
</pre></div>
</div>
<p>首先是检查NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST，对就是客户端的请求类型，就这三种，其他一律NGX_HTTP_NOT_ALLOWED。</p>
<p>其次是检查请求的url的结尾字符是不是斜杠‘/’，如果是说明请求的不是一个文件，给后续的handler去处理，比如后续的ngx_http_autoindex_handler（如果是请求的是一个目录下面，可以列出这个目录的文件），或者是ngx_http_index_handler（如果请求的路径下面有个默认的index文件，直接返回index文件的内容）。</p>
<p>然后接下来调用了一个ngx_http_map_uri_to_path函数，该函数的作用是把请求的http协议的路径转化成一个文件系统的路径。</p>
<p>然后根据转化出来的具体路径，去打开文件，打开文件的时候做了2中检查，一种是，如果请求的文件是个symbol link，根据配置，是否允许符号链接，不允许返回错误。还有一个检查是，如果请求的是一个名称，是一个目录的名字，也返回错误。如果都没有文件，就读取文件，返回内容。其实说返回内容可能不是特别准确，比较准确的说法是，把产生的内容传递给后续的filter去处理。</p>
</div>
<div class="section" id="http-log-module">
<h3>http log module<a class="headerlink" href="#http-log-module" title="永久链接至标题">¶</a></h3>
<p>该模块提供了对于每一个http请求进行记录的功能，也就是我们见到的access.log。当然这个模块对于log提供了一些配置指令，使得可以比较方便的定制access.log。</p>
<p>这个模块的代码位于src/http/modules/ngx_http_log_module.c，虽然这个模块的代码有接近1400行，但是主要的逻辑在于对日志本身格式啊，等细节的处理。我们在这里进行分析主要是关注，如何编写一个log handler的问题。</p>
<p>由于log handler的时候，拿到的参数也是requset这个东西，那么也就意味着我们如果需要，可以好好研究下这个结构，把我们需要的所有信息都记录下来。</p>
<p>对于log handler，有一点特别需要注意的就是，log handler是无论如何都会被调用的，就是只要服务端接受到了一个客户端的请求，也就是产生了一个requset对象，那么这些个log handler的处理函数都会被调用的，就是在释放requset的时候被调用的（ngx_http_free_request函数）。</p>
<p>那么当然绝对不能忘记的就是log handler最好，也是建议被挂载在NGX_HTTP_LOG_PHASE阶段。因为挂载在其他阶段，有可能在某些情况下被跳过，而没有执行到，导致你的log模块记录的信息不全。</p>
<p>还有一点要说明的是，由于nginx是允许在某个阶段有多个handler模块存在的，根据其处理结果，确定是否要调用下一个handler。但是对于挂载在NGX_HTTP_LOG_PHASE阶段的handler，则根本不关注这里handler的具体处理函数的返回值，所有的都被调用。如下，位于src/http/ngx_http_request.c中的ngx_http_log_request函数。</p>
<div class="highlight-none"><div class="highlight"><pre>static void
ngx_http_log_request(ngx_http_request_t *r)
{
        ngx_uint_t                  i, n;
        ngx_http_handler_pt        *log_handler;
        ngx_http_core_main_conf_t  *cmcf;

        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

        log_handler = cmcf-&gt;phases[NGX_HTTP_LOG_PHASE].handlers.elts;
        n = cmcf-&gt;phases[NGX_HTTP_LOG_PHASE].handlers.nelts;

        for (i = 0; i &lt; n; i++) {
                log_handler[i](r);
        }
}
</pre></div>
</div>
</div>
</div>
</div>


          </div>
        </div>
      </div>
      <div class="sphinxsidebar">
        <div class="sphinxsidebarwrapper">
            <p class="logo"><a href="http://tengine.taobao.org/">
              <img class="logo" src="_static/logo.png" alt="Logo"/>
            </a></p>
  <h3><a href="index.html">內容目录</a></h3>
  <ul>
<li><a class="reference internal" href="#">handler模块(100%)</a><ul>
<li><a class="reference internal" href="#handler">handler模块简介</a></li>
<li><a class="reference internal" href="#id1">模块的基本结构</a><ul>
<li><a class="reference internal" href="#id2">模块配置结构</a></li>
<li><a class="reference internal" href="#id3">模块配置指令</a></li>
<li><a class="reference internal" href="#id4">模块上下文结构</a></li>
<li><a class="reference internal" href="#id5">模块的定义</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id6">handler模块的基本结构</a></li>
<li><a class="reference internal" href="#id7">handler模块的挂载</a><ul>
<li><a class="reference internal" href="#id8">按处理阶段挂载</a></li>
<li><a class="reference internal" href="#id9">按需挂载</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id10">handler的编写步骤</a></li>
<li><a class="reference internal" href="#hello-handler">hello handler 模块</a></li>
<li><a class="reference internal" href="#id11">handler模块的编译和使用</a><ul>
<li><a class="reference internal" href="#config">config文件的编写</a></li>
<li><a class="reference internal" href="#id12">编译</a></li>
<li><a class="reference internal" href="#id13">使用</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id14">部分handler模块的分析</a><ul>
<li><a class="reference internal" href="#http-access-module">http access module</a></li>
<li><a class="reference internal" href="#http-static-module">http static module</a></li>
<li><a class="reference internal" href="#http-log-module">http log module</a></li>
</ul>
</li>
</ul>
</li>
</ul>

  <h4>上一个主题</h4>
  <p class="topless"><a href="chapter_2.html"
                        title="上一章">nginx平台初探(30%)</a></p>
  <h4>下一个主题</h4>
  <p class="topless"><a href="chapter_4.html"
                        title="下一章">过滤模块 (90%)</a></p>
  <h3>本页</h3>
  <ul class="this-page-menu">
    <li><a href="_sources/chapter_3.txt"
           rel="nofollow">显示源代码</a></li>
  </ul>
<div id="searchbox" style="display: none">
  <h3>快速搜索</h3>
    <form class="search" action="search.html" method="get">
      <input type="text" name="q" />
      <input type="submit" value="搜索" />
      <input type="hidden" name="check_keywords" value="yes" />
      <input type="hidden" name="area" value="default" />
    </form>
    <p class="searchtip" style="font-size: 90%">
    输入相关的模块，术语，类或者函数名称进行搜索
    </p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
        </div>
      </div>
      <div class="clearer"></div>
    </div>
    <div class="related">
      <h3>导航</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="genindex.html" title="总目录"
             >索引</a></li>
        <li class="right" >
          <a href="chapter_4.html" title="过滤模块 (90%)"
             >下一页</a> |</li>
        <li class="right" >
          <a href="chapter_2.html" title="nginx平台初探(30%)"
             >上一页</a> |</li>
        <li><a href="index.html">Nginx开发从入门到精通</a> &raquo;</li>
          <li><a href="module_development.html" >上篇：nginx模块开发篇</a> &raquo;</li> 
      </ul>
    </div>
    <div class="footer">
        &copy; 版权所有 2012, taobao.
      使用 <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
    </div>
  </body>
</html>